package com.orange.framework.interceptor;

import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.orange.common.annotation.NotRepeatSubmit;
import com.orange.common.base.entity.AppInfo;
import com.orange.common.constant.HeaderConstants;
import com.orange.common.enums.CallSourceEnum;
import com.orange.common.enums.ResultCodeEnum;
import com.orange.common.exception.ApiSecurityException;
import com.orange.common.exception.BusinessException;
import com.orange.common.utils.ApiUtil;
import com.orange.common.utils.MD5Util;
import com.orange.common.utils.StringUtil;
import com.orange.framework.wrapper.CachedBodyHttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Api检查具体方法
 *
 * @Author YWF
 * @Date 2025/5/27 15:57
 */
public class ApiSecurityInterceptor extends AbstractSecurityInterceptor {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    protected void rateLimiter(HttpServletRequest request) {
        // 接口限流校验
        String repeatKey = ApiUtil.generateRateLimitKey(request);
        logger.info("限流Key:{}", repeatKey);
        Long count = redisCache.inc(repeatKey);
        if (count != null && count == 1) {
            redisCache.expire(repeatKey, RATE_LIMIT_TIME_MILLIS);
        }
        if (count != null && count > RATE_LIMIT_COUNT) {
            throw new ApiSecurityException(ResultCodeEnum.LIMIT_ERROR);
        }
    }

    @Override
    protected void signParamsCheck(HttpServletRequest request) {
        String appKey = request.getHeader(HeaderConstants.X_APPKEY);
        String timestamp = request.getHeader(HeaderConstants.X_TIMESTAMP);
        String nonce = request.getHeader(HeaderConstants.X_NONCE);
        String sign = request.getHeader(HeaderConstants.X_SIGN);
        // 前端传入此参数，用于后端问题排查，当出现接口异常时，可以检查到底是那个版本的APP调用接口出现了问题
        String appVersion = request.getHeader(HeaderConstants.APP_VERSION);

        if (StringUtil.isAnyBlank(appKey, timestamp, nonce, sign)) {
            throw new BusinessException(ResultCodeEnum.SING_MISS_PARAM_ERROR);
        }
        /*检验时间戳类型*/
        try {
            Long.valueOf(timestamp);
        } catch (NumberFormatException e) {
            throw new BusinessException(ResultCodeEnum.REQUEST_TIME_INVALID);
        }

        // 开发者账号信息校验
        appKeyParamsCheck(appKey, appVersion);
    }

    private AppInfo appKeyParamsCheck(String appKey, String appVersion) {
        if (StringUtil.isBlank(appKey)) {
            throw new BusinessException(ResultCodeEnum.APPKEY_NOT_EMPTY);
        }
        AppInfo appInfo = redisCache.hget(appInfoCacheKey, appKey);
        if (ObjectUtil.isEmpty(appInfo)) {
            throw new BusinessException(ResultCodeEnum.APPKEY_IS_INVALID);
        }
        String callSource = appInfo.getAppType();
        /*请求来源必须合法*/
        if (!CallSourceEnum.isValid(callSource)) {
            throw new BusinessException(ResultCodeEnum.REQUEST_SOURCE_INVALID);
        }
        /*请求来源校验：Android和ios必须要携带App版本号*/
        if ((CallSourceEnum.ANDROID.name().equals(callSource) || CallSourceEnum.IOS.name().equals(callSource)) && StringUtil.isEmpty(appVersion)) {
            throw new BusinessException(ResultCodeEnum.APP_VERSION_IS_INVALID);
        }
        return appInfo;
    }

    @Override
    protected void repeatSubmitCheck(HttpServletRequest request, Object handler) {
        String appKey = request.getHeader(HeaderConstants.X_APPKEY);
        String timestamp = request.getHeader(HeaderConstants.X_TIMESTAMP);
        String nonce = request.getHeader(HeaderConstants.X_NONCE);
        CachedBodyHttpServletRequest cachedRequest = (CachedBodyHttpServletRequest) request;
        // 请求时间过期校验
        NotRepeatSubmit notRepeatSubmit = ApiUtil.getNotRepeatSubmit(handler);
        long expireTime = notRepeatSubmit == null ? REQUEST_OUT_TIME : notRepeatSubmit.value();
        long now = System.currentTimeMillis();
        long ts = Long.parseLong(timestamp);
        if (Math.abs(now - ts) > expireTime) {
            // 请求过期
            throw new ApiSecurityException(ResultCodeEnum.REQUEST_TIME_OUT);
        }
        Map<String, String> params = collectAllParams(cachedRequest);
        // 重复请求校验
        String key = appKey + ":" + nonce + ":" + MD5Util.encode(params.toString());
        Boolean result = redisCache.setIf(key, now, expireTime,
                TimeUnit.MILLISECONDS);
        if (result == null || !result) {
            throw new ApiSecurityException(ResultCodeEnum.NO_REPEAT_REQUEST);
        }
    }

    @Override
    protected void verifySign(HttpServletRequest request) {

    }


    /**
     * 请求参数组装
     *
     * @param request
     * @return
     */
    public Map<String, String> collectAllParams(CachedBodyHttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        try {
            // 获取请求体内容
            String body = new String(ApiUtil.readBytes(request.getInputStream()), StandardCharsets.UTF_8);
            if (StringUtils.hasText(body)) {
                if (StringUtils.hasText(body)) {
                    try {
                        Map<String, Object> json = objectMapper.readValue(body, Map.class);
                        for (Map.Entry<String, Object> entry : json.entrySet()) {
                            map.put(entry.getKey(), entry.getValue() == null ? "" : entry.getValue().toString());
                        }
                    } catch (Exception e) {
                        logger.error("Failed to parse request body: {}", body, e);
                        throw new ApiSecurityException(ResultCodeEnum.REQUEST_BODY_ERROR);
                    }
                }
            } else {
                // 如果没有请求体，则获取URL参数
                Enumeration<String> parameterNames = request.getParameterNames();
                while (parameterNames.hasMoreElements()) {
                    String name = parameterNames.nextElement();
                    String value = request.getParameter(name);
                    map.put(name, value);
                }
            }
        } catch (Exception e) {
            logger.error("Failed to read request body", e);
            throw new ApiSecurityException(ResultCodeEnum.REQUEST_BODY_ERROR);
        }
        return map;
    }
}
